package wortubung.wordlist;

import java.io.File;
import java.io.LineNumberReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;

import wortubung.WortUbung;
import wortubung.core.LANG;
import wortubung.core.NounCategory;
import wortubung.core.POSCategory;
import wortubung.core.WORD;
import wortubung.lang.LanguageManager;
import wortubung.util.CommonUtils;
import wortubung.util.PrintableDelegate;

import static wortubung.util.CommonUtils.*;

public class WordQuery extends PrintableDelegate implements Comparator<WORD> {
	public enum QueryType {
		HELP,
		LISTS,
		POS,
		NOUN_CATEGORY,
		WORDS_IN_FILE,
		FIXED_USAGE,
		IDIOM,
		SLANG,
		LOCUTION,
		LATEST,
		ERROR
	};

	public static final int UPPER_LEVEL = 5;
	public static final int LOWER_LEVEL = 0;

	protected int total = Integer.MAX_VALUE;
	private int upperLevel;
	private int lowerLevel;
	private int pageWidth;
    private int maxWordLen = 5;
    private int maxWordWithAliasLen = 5;
	private QueryType type;
	private Object value;
    private String outputFileName = null;

	public WortUbung getDictionary() { return (WortUbung)getPrintable(); }
	public boolean isHelp() { return type == QueryType.HELP; }
	public boolean isError() { return type == QueryType.ERROR; }
	public int getTotal() { return total; }
    public int getUpperLevel() { return upperLevel; }
    public int getLowerLevel() { return lowerLevel; }
    public int getPageWidth() { return pageWidth; }
    public int getWordMaxLen() { return maxWordLen; }
    public int getWordWithAliasMaxLen() { return maxWordWithAliasLen; }
    public String[] getListShortNames() { return (String[]) value; }
    public POSCategory getPOS() { return (POSCategory) value; }
    public NounCategory getNounCategory() { return (NounCategory) value; }
    public String[] getWordsFileNames() { return ((String) value).split(","); }
    public String getOutputFileName() { return outputFileName; }

    public WordQuery(WortUbung dictionary, String params) {
    	super(dictionary);

    	this.upperLevel = 5;
        this.lowerLevel = 0;
        this.pageWidth = dictionary.getPageWidth();

		if (isEmpty(params) || params.equals("-?") || params.equals("?")) {
			this.type = QueryType.HELP;
			return;
		}

		this.type = QueryType.ERROR;
		for (String opt : params.split(" +")) {
			String[] keyVal = opt.split("=");
			String queryType = keyVal[0].toLowerCase();
			if ("levels".equalsIgnoreCase(queryType)) {
				String[] lowerUpper = keyVal[1].split("-");
				try {
					lowerLevel = Integer.parseInt(lowerUpper[0]);
					upperLevel = Integer.parseInt(lowerUpper[1]);
				} catch(Exception e) {
					error("Error setting lower/upper level: ", params);
				}
			}
			else if ("pagewidth".equalsIgnoreCase(queryType)) {
				try {
					pageWidth = Integer.parseInt(keyVal[1]);
				} catch(Exception e) {
					error("Invalid page width: ", keyVal[1]);
				}
			}
			else if ("out".equalsIgnoreCase(queryType)) {
				outputFileName = keyVal[1];
			}
			else if ("source".equalsIgnoreCase(queryType)) {
				keyVal = keyVal[1].split(":");
				queryType = keyVal[0].toLowerCase();
    			if (keyVal.length < 2) {
    				queryType = queryType.toLowerCase();
        			if ("latest".equals(queryType))
    					type = QueryType.LATEST;
        			else if (queryType.startsWith("fixed"))
        				type = QueryType.FIXED_USAGE;
        			else if (queryType.startsWith("idiom"))
        				type = QueryType.IDIOM;
        			else if (queryType.startsWith("slang"))
        				type = QueryType.SLANG;
        			else if (queryType.startsWith("loc"))
        				type = QueryType.LOCUTION;
					else
						error("Invalid source value.");
					continue;
				}
				if ("lists".equalsIgnoreCase(queryType)) {
					type = QueryType.LISTS;
    				if (keyVal[1].equalsIgnoreCase("all")) {
    					value = null;
    				}
    				else {
    					String[] names = keyVal[1].split(",");
    					for (int i=0; i<names.length; ++i)
    						if (names[i].length() == 1)
    							names[i] = "0" + names[i];
    					value = names;
    				}
    			}
    			else if ("pos".equalsIgnoreCase(queryType)) {
    				if (keyVal[1].equalsIgnoreCase("all")) {
    					type = QueryType.POS;
    					value = null;
    				} else {
    					try {
    						value = POSCategory.valueOf(keyVal[1]);
        					type = QueryType.POS;
    					} catch(Exception e) {
    						error("Unrecognized POS: ", keyVal[1], ". Valid ones are:");
    					    error(concatEvenly("  ", false, getPageWidth(), -1, " ", POSCategory.values()));
    					}
    				}
    			}
    			else if ("nouncat".equalsIgnoreCase(queryType)) {
					if (keyVal[1].equalsIgnoreCase("all")) {
						type = QueryType.NOUN_CATEGORY;
						value = null;
    				} else {
    					try {
    						value = NounCategory.valueOf(keyVal[1]);
    						type = QueryType.NOUN_CATEGORY;
    					} catch(Exception e) {
    						error("Unrecognized noun category: ", keyVal[1], ".\nValid noun categories are:");
    					    error(concatEvenly("  ", false, getPageWidth(), -1, " ", NounCategory.values()));
    					}
    				}
    			}
    			else if ("file".equalsIgnoreCase(queryType)) {
					type = QueryType.WORDS_IN_FILE;
					value = keyVal[1];
    			}
    		}
		}
    }

    public boolean fit(WORD word) {
        int level = word.getLevel();
        if (level > upperLevel || level < lowerLevel)
            return false;

        switch(type) {
        case LISTS:
        	String[] listShortNames = getListShortNames();
        	if (listShortNames == null)
        		return true;
        	for (String x : listShortNames) {
        		if (word.getWordListInfo().getShortName().equals(x))
        			return true;
        	}
       		return false;
        case POS:
        	POSCategory posc = getPOS();
        	return (posc == null) || (word.getPOSCategory() == posc);
        case NOUN_CATEGORY:
        	NounCategory nc = getNounCategory();
        	return (nc == null) || (word.isNoun() && word.getDefaultPOSInfo().getNounCategory() == nc);
        case FIXED_USAGE:
        	return word.numFixedUsages() > 0;
        case IDIOM:
        	return word.numIdioms() > 0;
        case SLANG:
        	return word.numSlangs() > 0;
        case LOCUTION:
        	return word.numLocutions() > 0;
        case LATEST:
        	return getDictionary().getCurrentLanguageManager().getLatestWordsSet().contains(word.getWord());
        }
        return false;
    }

    public WORD[] collectWords(boolean sort) {
		maxWordLen = maxWordWithAliasLen = 5;
    	ArrayList<WORD> list = new ArrayList<WORD>();
    	for (WORD wd : getDictionary().getCurrentLanguageManager().getWords()) {
    		if (fit(wd)) {
    			list.add(wd);
				if (wd.getWord().length() > maxWordLen)
					maxWordLen = wd.getWord().length();
				if (wd.getWordWithAlias().length() > maxWordWithAliasLen)
					maxWordWithAliasLen = wd.getWordWithAlias().length();
    		}
    	}
    	WORD[] ret = list.toArray(new WORD[list.size()]);
    	if (sort)
    		Arrays.sort(ret);
    	return ret;
    }

    private HashSet<String> readWordFile() throws Exception {
		maxWordLen = maxWordWithAliasLen = 5;
		HashSet<String> set = new HashSet<String>(); // to remove dups
		for (String fn : getWordsFileNames()) {
    		LineNumberReader lnr = openFile(new File(fn));
    		String words;
    		while ((words = lnr.readLine()) != null) {
    			for (String word : words.split(",")) {
        			word = cleanseWord(word);
        			if (word.length() > maxWordLen)
        				maxWordLen = word.length();
        			if (word.length() > maxWordWithAliasLen)
        				maxWordWithAliasLen = word.length();
        			set.add(word);
    			}
    		}
    		lnr.close();
		}
		return set;
    }

    public String[] getWords(LANG lang) {
		String[] ret = null;
		if (type == QueryType.WORDS_IN_FILE) {
			try {
    			ret = _sortWords(readWordFile());
    			lang.toVernacular(ret);
			} catch(Exception e) {
				error(e);
			}
		} else {
			WORD[] words = collectWords(true);
			ret = new String[length(words)];
			for (int i=ret.length-1; i>=0; --i)
				ret[i] = words[i].getWordWithAlias();
		}
		return ret;
	}

	public WORD[] query(LanguageManager lm, boolean sort) {
		WORD[] words = null;
		if (type == QueryType.WORDS_IN_FILE) {
			try {
				maxWordLen = maxWordWithAliasLen = 5;
				ArrayList<WORD> list = new ArrayList<WORD>();
				for (String word : readWordFile()) {
					Object x = lm.getWordMap().get(word);
					if (x == null)
						promptln("Word '", word, "' is not found.");
					else if (x instanceof WORD) {
						WORD w = (WORD)x;
						if (w.hasAlias()) {
							int len = w.getWordWithAlias().length();
							if (len > maxWordWithAliasLen)
								maxWordWithAliasLen = len;
						}
						list.add(w);
					} else {
						for (WORD w : ((WORD[])x)) {
							if (w.hasAlias()) {
								int len = w.getWordWithAlias().length();
								if (len > maxWordWithAliasLen)
									maxWordWithAliasLen = len;
							}
							list.add(w);
						}
					}
				}
				words = list.toArray(new WORD[list.size()]);
				if (sort)
					Arrays.sort(words);
			} catch(Exception e) {
				error(e);
			}
		}
		else {
			words = collectWords(sort);
		}
		
		if (words != null && words.length > total)
   			words = Arrays.copyOf(words, total);
		return words;
	}

    public int compare(WORD fromWord, WORD toWord) {
    	int ret = compareIgnoreCase(fromWord.getAnglicizedWord(), toWord.getAnglicizedWord());
        if (ret != 0)
        	return ret;

        ret = compareIgnoreCase(fromWord.getAlias(), toWord.getAlias());
        if (ret != 0)
        	return ret;

        return CommonUtils.compare(toWord.isReflexiveVerb(), fromWord.isReflexiveVerb());
    }

	public String getHelpText() {
		final String helpText =
   	             "    pageWidth=<number>             Set the page width.\n" +
                 "    levels=0-5                     Select words in the range.\n" +
	             "    source=lists:<sn>(,<sn>)*|all  Use the words in these (dictionary) word lists.\n" +
	             "    source=pos:<POS>|all           Use the words in the specified POS category.\n" +
	             "    source=nounCat:<noun_cat>|all  Use the words in the specified noun category.\n" +
	             "    source=file:<file>(,<file>)*   Use the words in the input file, one word per line.\n" +
	             "    source=fixedUsage(s)           Use the words with idioms, locutions, and/or slangs.\n" +
	             "    source=idiom(s)                Use the words with idioms.\n" +
	             "    source=slang(s)                Use the words with slangs.\n" +
	             "    source=locution(s)             Use the words with locutions.\n" +
	             "    source=latest                  Use the latest visited words.\n\n" +

	             "    For page width, here are some values if you plan to print from Microsoft Word:\n" +
	             "         (Margins: 0.5\" all around;  Font: Consolas)\n" +
	             "      1. (Layout: PORTRAIT;   Font-size: 9):  pageWidth = 109.\n" +
	             "      2. (Layout: PORTRAIT;   Font-size: 8):  pageWidth = 122.\n" +
	             "      3. (Layout: LANDSCAPE;  Font-size: 9):  pageWidth = 145.\n" +
	             "      4. (Layout: LANDSCAPE;  Font-size: 8):  pageWidth = 163.\n";
		return helpText;	}
}
